/**
 *   @file Thread.cpp
 */
#include "Thread.h"
#include "Event.h"
#include "../common/CppMemPool.h"
#include "../collections/HashtableT.h"
#if defined(WIN32)
#   include <windows.h>
#elif defined(linux)
#   include <stdlib.h>
#   include <pthread.h>
#   include <unistd.h>
#   include <sys/select.h>
#endif

#ifdef _DEBUG
#   include <assert.h>
#endif


#if defined(WIN32)

typedef u32_t tlskeyslot_t;

#elif defined(linux)

typedef pthread_key_t tlskeyslot_t;

PRIVATE void * TlsGetValue(pthread_key_t key)
{
   return pthread_getspecific(key);
}

PRIVATE bool_t TlsSetValue(pthread_key_t key, const void *value)
{
   if (pthread_setspecific(key, value) == 0)
      return TRUE;

   return FALSE;
}

#endif
//! This is defined in MemReleasePool.c
extern "C" tlskeyslot_t MRP_GetNamedDataSlot();

using namespace cppflib::collections;

namespace cppflib
{

namespace threading
{

/** 
 *  Thread info structure
 */
class ThreadInfo : public BaseObject
{
private:
   ThreadInfo() { }
   
public:
   ThreadEntryPoint *pThreadEntry;
   ptr_t pParam;
#if defined(WIN32)
   HANDLE hThread;
   // 
   // GetThreadId function is only available on Windows Vista or later, thus, the thread ID
   // is saved explicitly here after CreateThread returns successfully
   u32_t threadId;
#elif defined(linux)
   pthread_t hThread;
#endif
   //
   // __private_ThreadEntry will wait on this event until it is signaled at the end of Thread::Start()
   // Sometimes, the callee thread may start faster than the caller thread so that some fields of 
   // Thread::pThreadInfo may not be initialized. This event is to ensure Thread::Start() has completed
   // all initialization steps before the spawned thread can execute.
   //
   Event *pStartThreadEvent; 
   Thread *pThreadObj;

   ThreadInfo(Thread *pThreadObj, ThreadEntryPoint *pThreadEntry)
   {
      this->pThreadObj = pThreadObj;     // direct assign to prevent circular reference
      this->pThreadEntry = pThreadEntry;
      cppmempool::Retain(this->pThreadEntry);
      this->pStartThreadEvent = new Event();
      cppmempool::Retain(this->pStartThreadEvent);
   }

   virtual ~ThreadInfo()
   {
      FreeResource();
   }

   void FreeResource()
   {
      if (pStartThreadEvent) {
         cppmempool::Release(pStartThreadEvent);
         pStartThreadEvent = NULL;
      }

      if (pThreadEntry) {
         cppmempool::Release(pThreadEntry);
         pThreadEntry = NULL;
      }

      this->pThreadObj = NULL;
   }
};

/**
 *  Clean up named data storage for this thread
 */
PRIVATE void __CleanupNamedDataStorage()
{
   void * pSlotVal = TlsGetValue(MRP_GetNamedDataSlot());
   if (pSlotVal != NULL) {
      cppmempool::Release(pSlotVal);
      TlsSetValue(MRP_GetNamedDataSlot(), NULL);
   }
}

/**
 *  Internal OS dependent thread entry point
 */
#if defined(WIN32)
u32_t __stdcall __private_ThreadEntry(void * pParam)
#elif defined(linux)
void * __private_ThreadEntry(void * pParam)
#endif
{
   ThreadInfo *pThreadInfo = static_cast<ThreadInfo*>(pParam);
   pThreadInfo->pStartThreadEvent->Wait();  // to wait until Thread::Start() has completed before running

   int ret = 0;
   if (pThreadInfo->pThreadEntry)
      ret = (*pThreadInfo->pThreadEntry)(pThreadInfo->pParam);
   else
      ret = pThreadInfo->pThreadObj->Run(pThreadInfo->pParam);

#ifdef _DEBUG
   // make sure all pools are released before leaving
   assert(cppmempool::GetPoolsCount() == 0);
#endif

   __CleanupNamedDataStorage();

#if defined(WIN32)
   return ret;
#elif defined(linux)
   return reinterpret_cast<void*>(ret);
#endif
}


/*************************** Thread ********************************/

/**
 *  Use 'Run' as thread function
 */
Thread::Thread(void)
{
   pThreadInfo = new ThreadInfo(this, NULL);
   cppmempool::Retain(pThreadInfo);
}

/**
 *  Use custom function as thread function
 */
Thread::Thread(const ThreadEntryPoint &threadEntry)
{
   pThreadInfo = new ThreadInfo(this, new ThreadEntryPoint(threadEntry));
   cppmempool::Retain(pThreadInfo);
}

Thread::~Thread(void)
{
   if (pThreadInfo) {
      Join();
      cppmempool::Release(pThreadInfo);
   }
}

bool Thread::Start(ptr_t pParam)
{
   this->pThreadInfo->pParam = pParam;

#if defined(WIN32)
   DWORD threadId = 0;
   HANDLE hThread = ::CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(&__private_ThreadEntry), 
                                   static_cast<LPVOID>(this->pThreadInfo), 0, &threadId);
   if (!hThread)
      return false;

   this->pThreadInfo->threadId = threadId;  // save thread ID copy
#elif defined(linux)
   pthread_t hThread = 0;
   if (pthread_create(&hThread, NULL, &__private_ThreadEntry, static_cast<void*>(this->pThreadInfo)) != 0)
      return false;
#endif

   this->pThreadInfo->hThread = hThread;

   this->pThreadInfo->pStartThreadEvent->Set(); // thread can start now

   return true;
}

/**
 *  Wait for thread to end
 *
 *  @return value return from thread
 */
ptr_t Thread::Join(void)
{
   ptr_t exitValue = 0;

   if (IsAlive()) {

#if defined(WIN32)
      DWORD exitCode = 0;
      ::WaitForSingleObject(pThreadInfo->hThread, INFINITE);
      ::GetExitCodeThread(pThreadInfo->hThread, &exitCode);
      exitValue = static_cast<ptr_t>(exitCode);
      ::CloseHandle(pThreadInfo->hThread);
       pThreadInfo->hThread = NULL;
#elif defined(linux)
      void * pRetVal = NULL;
      pthread_join(pThreadInfo->hThread, &pRetVal);
      exitValue = reinterpret_cast<ptr_t>(pRetVal);
      pThreadInfo->hThread = 0;      
#endif
   }
   
   return exitValue;
}

bool Thread::IsAlive(void)
{
   return (pThreadInfo && pThreadInfo->hThread);
}

/**
 *  Get ID of this thread
 */

ptr_t Thread::GetId(void)
{
   if (IsAlive()) {
#if defined(WIN32)
      //
      // ::GetThreadId(pThreadInfo->hThread) is only available on Windows Vista or later
      //
      return pThreadInfo->threadId;
#elif defined(linux)
      return pThreadInfo->hThread;
#endif
   }

   return 0;
}


/**
 *  Sleep in milliseconds
 */
void Thread::Sleep(int sleepTime)
{
#if defined(WIN32)
   ::Sleep(sleepTime);
#elif defined(linux)
   struct timeval tv = {sleepTime / 1000, (sleepTime % 1000) * 1000};
   ::select(0, NULL, NULL, NULL, &tv);
#endif
}


/**
 *  Get ID of current thread context
 */
ptr_t Thread::GetCurrentThreadId(void)
{
#if defined(WIN32)
   return ::GetCurrentThreadId();
#elif defined(linux)
   return pthread_self();
#endif
}


/**
 *  Leave for derived class to implement it if needed
 *
 *  @return any status code
 */
int Thread::Run(ptr_t pParam)
{
   return 0;
}

/**
 *  Get a thread local value 
 *
 *  @param [in] key -- any string to locate the value
 *
 *  @return NULL if not found
 */
BaseObject * Thread::GetNamedData(const CFString &key)
{
    void * pSlotVal = TlsGetValue(MRP_GetNamedDataSlot());
    if (pSlotVal == NULL)
        return NULL;

    HashtableT<CFString, BaseObject> *pTbl = reinterpret_cast<HashtableT<CFString, BaseObject> *>(pSlotVal);
    return pTbl->Get(key);
}

/**
 *  Set a new thread local value (old one will be replaced)
 *
 *  @param [in] key -- any string to locate the value
 */
void Thread::SetNamedData(const CFString &key, BaseObject *pData)
{
    void * pSlotVal = TlsGetValue(MRP_GetNamedDataSlot());
    HashtableT<CFString, BaseObject> *pTbl = NULL;
    if (pSlotVal == NULL) {
       pTbl = new HashtableT<CFString, BaseObject>();
       cppmempool::UnlinkFromPool(pTbl); // this table will be cleaned up when thread exits (individual thread) 
                                         // and in MRP_GlobalFinish (main thread)
       TlsSetValue(MRP_GetNamedDataSlot(), reinterpret_cast<void*>(pTbl));
    }
    else {
       pTbl = reinterpret_cast<HashtableT<CFString, BaseObject> *>(pSlotVal);
    }
    pTbl->Set(new CFString(key), pData);
}

/**
 *  Remove a thread local value 
 *
 *  @param [in] key -- any string to locate the value
 *
 *  @return false if key not found
 */
bool Thread::RemoveNamedData(const CFString &key)
{
    void * pSlotVal = TlsGetValue(MRP_GetNamedDataSlot());
    if (pSlotVal == NULL)
        return false;

    HashtableT<CFString, BaseObject> *pTbl = reinterpret_cast<HashtableT<CFString, BaseObject> *>(pSlotVal);
    return pTbl->Remove(key);
}


} // end of namespace threading


} // end of namespace cppflib

